package com.linjacy.httpproject.interceptor;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.linjacy.httpproject.annotation.NeedToAuth;
import com.linjacy.httpproject.builder.RedisUserBuilder;
import com.linjacy.httpproject.entity.User;
import com.linjacy.httpproject.service.UserService;
import com.linjacy.httpproject.thread.UserHolder;
import com.linjacy.httpproject.util.JwtUtil;
import com.mysql.cj.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.invoke.MethodHandle;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.logging.SimpleFormatter;

@Component
public class AuthInterceptor implements HandlerInterceptor {
    private static Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.remove();
    }

    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HandlerMethod methodHandle = (HandlerMethod) handler;
        NeedToAuth annotation = methodHandle.getMethod().getAnnotation(NeedToAuth.class);
        //无注解则无需校验
        if(annotation == null){
            return true;
        }
        //没token就不必放行
        String token = request.getHeader("token");
        if(StringUtils.isNullOrEmpty(token)){
            logger.error("权限校验失败");
            return false;
        }
        String userId = JwtUtil.getUserIdByToken(request);
        User user = userService.getUserById(Optional.ofNullable(userId).orElse(""));
        //查不到用户也是无效token
        if(!Optional.ofNullable(user).isPresent()){
            logger.error("权限校验失败,用户信息为空");
            return false;
        }
        UserHolder.set(user);
        //刷新token
        if(!refreshToken(user, token)){
            logger.error("刷新token失败");
            return false;
        }
        return true;
    }

    private boolean refreshToken(User user, String token) {
        //不存在该key的话直接废掉
        if(!redisTemplate.hasKey(user.getUserName() + JwtUtil.REDIS_TOKEN)){
            return false;
        }
        String redisToken = redisTemplate.opsForHash().get(user.getUserName() + JwtUtil.REDIS_TOKEN,"token").toString();
        //传来的token跟缓存中的不一致
        if(!token.equals(redisToken)){
            return false;
        }
        try {
            JwtUtil.verify(token);
        }catch (TokenExpiredException tokenExpiredException){
            //如果过期重新刷新redis中的token值
            RedisUserBuilder redisUserBuilder = new RedisUserBuilder();
            Map<String, Object> redisUserHash = redisUserBuilder.build();
            redisUserHash.putAll(redisTemplate.opsForHash().entries(user.getUserName() + JwtUtil.REDIS_TOKEN));
            //检查是否超过了限制
            if(!checkTheLoginTimes(redisUserHash)){
                return false;
            }
            redisUserBuilder.buildLoginTimesAndIncrement();
            redisTemplate.opsForHash().putAll(user.getUserName() + JwtUtil.REDIS_TOKEN,redisUserHash);
            redisTemplate.expire(user.getUserName() + JwtUtil.REDIS_TOKEN,JwtUtil.EXPIRE_TIME * 2, TimeUnit.MINUTES);
        }catch (Exception exception){
            return false;
        }
        return true;
    }

    private boolean checkTheLoginTimes(Map<String, Object> redisUserHash) {
        try {
            String loginTime = (String) redisUserHash.get("loginTime");
            Integer loginTimes = (Integer) redisUserHash.get("loginTimes");
            Date date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(loginTime);
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.DAY_OF_MONTH,-1);
            if(calendar.getTime().before(date)){
                if(loginTimes > RedisUserBuilder.maxLoginTimes){
                    logger.error("超过登录上线，请重新登录");
                    return false;
                }
            }
        } catch (ParseException e) {
            logger.error("日期转换失败，请检查格式");
            return false;
        }catch (Exception e){
            e.printStackTrace();
            logger.error("出现其他异常");
            return false;
        }
        return true;
    }
}
